package com.jxs.easysecurity.session;

import com.jxs.easysecurity.auth.Authentication;
import com.jxs.easysecurity.cache.MapSessionCache;
import com.jxs.easysecurity.cache.SessionCache;
import com.jxs.easysecurity.exception.AccessTokenResolveException;
import com.jxs.easysecurity.exception.SecurityException;
import com.jxs.easysecurity.token.*;
import com.jxs.easysecurity.token.impl.DefaultAccessTokenFactory;
import org.apache.commons.lang3.StringUtils;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.http.HttpServletRequest;
import java.util.Calendar;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Logger;

/**
 * @author jiangxingshang
 * @since 17/6/21
 */
public class SessionManager implements ServletRequestListener {

    private static final Logger log = Logger.getLogger(SessionManager.class.getName());

    private static ThreadLocal<HttpServletRequest> requestThreadLocal = new ThreadLocal<>();
    private static ThreadLocal<Session> sessionThreadLocal = new ThreadLocal<>();

    private int accessTokenTimeout = 60 * 60 * 24;
    private int sessionTimeout = 60 * 60 * 2;
    private SessionCache cache;
    private LoginHandler loginHandler;
    private AccessTokenFactory accessTokenBuilder;
    private OnAccessTokenBuildListener onAccessTokenBuildListener;
    private OnAccessTokenResolveListener onAccessTokenResolveListener;

    /**
     * 使用{@link MapSessionCache}作为默认的缓存。
     * @param handler
     */
    public SessionManager(LoginHandler handler) {
        this.loginHandler = handler;
        this.cache = new MapSessionCache();
    }

    public SessionManager(LoginHandler handler, SessionCache cache) {
        this.loginHandler = handler;
        this.cache = cache;
    }


    /**
     * 访问凭证的有效时间。该有效时间不会续时，过了后凭证将永久失效。
     * @param accessTokenTimeout 单位秒，默认24小时
     */
    public void setAccessTokenTimeout(int accessTokenTimeout) {
        this.accessTokenTimeout = accessTokenTimeout;
    }

    /**
     * 缓存的有效时间，每次请求都会延续这个时间，也就是当用户在一定时间内没有做任何操作，缓存就会失效。
     * @param sessionTimeout 单位秒，默认2小时
     */
    public void setSessionTimeout(int sessionTimeout) {
        this.sessionTimeout = sessionTimeout;
    }


    public void setCache(SessionCache cache) {
        if(this.cache != null && this.cache instanceof MapSessionCache) {
            ((MapSessionCache) this.cache).destroy();
        }
        this.cache = cache;
    }

    /**
     * 访问凭证加密的密钥
     * @param secretKey
     */
    public void setAccessTokenSecretKey(String secretKey) {
        setAccessTokenBuilder(new DefaultAccessTokenFactory(secretKey));
    }

    public void setAccessTokenBuilder(AccessTokenFactory accessTokenBuilder) {
        this.accessTokenBuilder = accessTokenBuilder;
    }

    public void setLoginHandler(LoginHandler loginHandler) {
        this.loginHandler = loginHandler;
    }

    public void setOnAccessTokenBuildListener(OnAccessTokenBuildListener onAccessTokenBuildListener) {
        this.onAccessTokenBuildListener = onAccessTokenBuildListener;
    }

    public void setOnAccessTokenResolveListener(OnAccessTokenResolveListener onAccessTokenResolveListener) {
        this.onAccessTokenResolveListener = onAccessTokenResolveListener;
    }

    public SessionWrapper login(Authentication authentication) throws SecurityException {
        LoginResult result = loginHandler.login(authentication);
        String sessionId = UUID.randomUUID().toString().toLowerCase().replaceAll("-", "");
        long timeout = Calendar.getInstance().getTimeInMillis() + accessTokenTimeout * 1000;
        AccessTokenEx token = accessTokenBuilder.build(sessionId, timeout, result.getTokenMergeInfo());
        if(onAccessTokenBuildListener != null) {
            String text = onAccessTokenBuildListener.build(result, authentication, token.toString());
            token.setToken(text);
        }

        Session session = new Session(token, result.getUserId(), result.getResourceId());

        //缓存用户的权限资源
        String cacheContent = result.getUserId() + ":" + StringUtils.join(result.getResourceId(), ",");
        cache.set(sessionId, cacheContent);
        updateSessionTimeout(session);

        return new SessionWrapper(session, result.getExtras());
    }

    /**
     *
     * @return 登出成功返回true，登出失败（可能session是无效或未登陆）返回false。
     */
    public boolean logout() {
        Session session = getSession();
        if(!session.isValid() || !session.isLogin()) {
            return false;
        } else {
            cache.del(session.getSessionId());
            return true;
        }
    }

    /**
     * 获取会话信息，并将会话信息保存在本地线程中，你可以在后续从缓存中获取到会话。
     * @return 永远不会返回空。
     */
    public Session getSession() {
        Session session = sessionThreadLocal.get();
        if(session != null) {
            return session;
        }
        session = getSession(AccessTokenReader.getAccessToken());
        sessionThreadLocal.set(session);
        return session;
    }

    /**
     * 使用存在的凭证获取会话，此方法不会将获取到的会话放到本地线程中保存，所以你后续使用getSession()可能不会得到一个有效的会话。
     * @param token
     * @return 永远不会返回空。
     */
    public Session getSession(String token) {
        Session session;
        if(StringUtils.isBlank(token)) {
            return Session.createInvalidSession();
        }
        if(onAccessTokenResolveListener != null) {
            token = onAccessTokenResolveListener.resolve(token);
        }
        try {
            AccessToken ac = accessTokenBuilder.resolve(token);
            if(cache.hasKey(ac.getSessionId())) {
                String content = cache.get(ac.getSessionId());
                String[] tmp = StringUtils.split(content, ":");
                String userId = tmp[0];
                Set<String> set = new HashSet<>();
                if(tmp.length > 1) {
                    for(String id : StringUtils.split(tmp[1], ",")) {
                        set.add(id);
                    }
                }
                session = new Session(ac, userId, set);
            } else {
                session = new Session(ac);
            }
            return session;
        } catch (AccessTokenResolveException e) {
            log.warning("Access token resolve fail -> " + token);
            return Session.createInvalidSession();
        }
    }

    /**
     * 更新session的缓存有效时间。
     * @see #setSessionTimeout(int)
     * @param session
     */
    public void updateSessionTimeout(Session session) {
        updateSessionTimeout(session.getSessionId());
    }

    private void updateSessionTimeout(String sid) {
        cache.expire(sid, sessionTimeout * 1000);
    }


    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        requestThreadLocal.remove();
        sessionThreadLocal.remove();
    }

    @Override
    public void requestInitialized(ServletRequestEvent requestEvent) {
        if (!(requestEvent.getServletRequest() instanceof HttpServletRequest)) {
            log.warning("Request is not an HttpServletRequest: " + requestEvent.getServletRequest());
        } else {
            requestThreadLocal.set((HttpServletRequest)requestEvent.getServletRequest());
        }
    }

    public static HttpServletRequest getRequest() {
        return requestThreadLocal.get();
    }
}
